﻿using System;
using System.Linq;
using VXCompany.SilverlightLogging.ServiceProxy;
using System.Collections.Generic;

namespace VXCompany.SilverlightLogging.MVVM.Models
{
	/// <summary>
	/// Wrapper for service proxy.
	/// </summary>
	public class ServiceHelper : NotifyingBase
	{
		private ExceptionPolicy exceptionPolicy;
		private bool _IsServiceAvailable;
		private const int MAX_BATCH_SIZE = 20;
		private static object _lockMe = new object();

		/// <summary>
		/// Configuration updated event.
		/// </summary>
		public event EventHandler<ConfigurationReceivedEventArgs> ConfigurationUpdated;

		/// <summary>
		/// Exception sync run completed (or failed).
		/// </summary>
		public event EventHandler<ExceptionsSyncedEventArgs> ExceptionsSynced;

		/// <summary>
		/// Indicates whether service is available.
		/// </summary>
		public bool IsServiceAvailable
		{
			get { return _IsServiceAvailable; }
			private set
			{
				_IsServiceAvailable = value;
				RaisePropertyChanged("IsServiceAvailable");
			}
		}

		private bool _IsSynchronizing;

		/// <summary>
		/// Indicates whether a synchronization attempt is being made.
		/// </summary>
		public bool IsSynchronizing
		{
			get { return _IsSynchronizing; }
			private set
			{
				_IsSynchronizing = value;
				RaisePropertyChanged("IsSynchronizing");
			}
		}

		/// <summary>
		/// Creates new instance, using policy for configurationvalues.
		/// </summary>
		/// <param name="exceptionPolicy"></param>
		public ServiceHelper(ExceptionPolicy exceptionPolicy)
		{
			if (exceptionPolicy.LoggingServiceClient == null)
			{
				throw new ArgumentNullException("exceptionPolicy");
			}

			this.exceptionPolicy = exceptionPolicy;			
		}

		/// <summary>
		/// Gets logging configuration settings from service.
		/// </summary>
		public void BeginGetConfig()
		{
			exceptionPolicy.LoggingServiceClient.GetClientConfigCompleted += (sender, ea) =>
			{
				if (this.ConfigurationUpdated != null)
				{
					if (ea.Error != null)
					{
						IsServiceAvailable = false;
						ConfigurationUpdated(this, new ConfigurationReceivedEventArgs(GetDefaultConfigurationValues()));
					}
					else
					{
						IsServiceAvailable = true;
						ConfigurationUpdated(this, new ConfigurationReceivedEventArgs(MapConfigurationValues(ea.Result)));
					}
				}
			};
			exceptionPolicy.LoggingServiceClient.GetClientConfigAsync();
		}

		/// <summary>
		/// Writes exeptions to service asynchronously.
		/// </summary>
		public void BeginWrite()
		{
			lock (_lockMe)
			{
				if (!IsServiceAvailable)
				{
					OnExceptionsSynced(Result.Unavailable);
				}
				else if (IsSynchronizing)
				{
					OnExceptionsSynced(Result.DuplicateSync);
				}
				else
				{
					var errors = exceptionPolicy.Store.GetByMinLevel(exceptionPolicy.LoggingConfiguration.MinSyncLevel).Take(MAX_BATCH_SIZE).ToObservableCollection();
					if (errors.Count > 0)
					{
						if (!exceptionPolicy.LoggingServiceClient.LogExceptionBatchCompletedHandlerAvailable)
						{
							exceptionPolicy.LoggingServiceClient.LogExceptionBatchCompleted += new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>(LoggingServiceClient_LogExceptionBatchCompleted);
						}
						IsSynchronizing = true;
						exceptionPolicy.LoggingServiceClient.LogExceptionBatchAsync(errors, errors);
						//result will be set after calling the service.
					}
					else
					{						
						if (exceptionPolicy.Store.Count > 0)
						{
							//there are errors, but not with configured level or higher.
							OnExceptionsSynced(Result.NoErrorsForCurrentLevelAvailable);
						}
						else
						{
							//there are no errors.
							OnExceptionsSynced(Result.NoErrorsAvailable);
						}
					}
				}
			}
		}

		/// <summary>
		/// Eventhandler for sync completed event. Clear sync'ed errors. 
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="ea"></param>
		private void LoggingServiceClient_LogExceptionBatchCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs ea)
		{
			try
			{
				if (ea.Error != null)
				{
					if (ea.Error is TimeoutException)
					{
						OnExceptionsSynced(Result.TimeOut);
					}
					else
					{
						IsServiceAvailable = false;
						exceptionPolicy.HandleException(ea.Error);
						OnExceptionsSynced(Result.Failure);
					}
				}
				else
				{
					IsServiceAvailable = true;
					OnExceptionsSynced(Result.Success);
					exceptionPolicy.Store.RemoveRange((IEnumerable<ExceptionMessage>)ea.UserState);
				}
			}
			finally
			{
				IsSynchronizing = false;
			}
		}

		/// <summary>
		/// Fires the ExceptionsSynced event.
		/// </summary>
		/// <param name="result"></param>
		private void OnExceptionsSynced(Result result)
		{
			if (ExceptionsSynced != null)
			{
				ExceptionsSynced(this, new ExceptionsSyncedEventArgs(result));
			}
		}

		/// <summary>
		/// Returns default configuration values.
		/// </summary>
		private LoggingConfiguration GetDefaultConfigurationValues()
		{
			return new LoggingConfiguration
			{
				SyncBehaviorFlags = SyncBehaviorFlags.AppStart | SyncBehaviorFlags.AppCrash,
				MinLogLevel = ErrorLevel.Informational,
				MinSyncLevel = ErrorLevel.Informational
			};
		}

		/// <summary>
		/// Returns LoggingConfigurationMessage mapped to LoggingConfiguration.
		/// </summary>
		private LoggingConfiguration MapConfigurationValues(LoggingConfigurationMessage message)
		{
			return new LoggingConfiguration
			{
				SyncBehaviorFlags = message.SyncBehaviorFlags,
				MinLogLevel = message.MinLogLevel,
				MinSyncLevel = message.MinSyncLevel,
				ClientAddress = message.ClientAddress,
				IsUserAuthenticated = message.IsUserAuthenticated,
				SyncWhenStorageUnavailable = message.SyncWhenStorageUnavailable,
				UserID = message.UserID
			};
		}
	}
}
